package com.frontalcode 
{
	/**                  
     * The Transitioner class implements transitions between managed elements.
     */
    public class Transitioner extends DocumentElement
    {
		public var transitions : Array = null;
		internal var delayTaskId : uint = 0;
		internal var started : Boolean = false;
		private var _target : Container = null; 
 		
		/**
		 * Creates a new Transitioner instance.
		 */
		public function Transitioner ( document : Document, parent : DocumentElement, node : XML, options : Object = null )
		{
			super ( document, parent, node, options );
		}
		
		public function get containerManager ( ) : ContainerManager { return parent as ContainerManager; }
		
		override public function getContainerByIndex ( index : *, stopAtChildren : Boolean = true, showWarnings : Boolean = true ) : Container { return containerManager.getContainerByIndex ( index, stopAtChildren, showWarnings ); }
		
		public function get target ( ) : Container 
		{ 
			if ( _target != null ) return _target;
			return getAttribute ( "target" ).toLowerCase ( ) == "next" ? containerManager.next : containerManager.current; 
		}
		
		public function set target ( value : Container ) : void 
		{ 
			_target = value; 
		}
		
		/**            
		 * @private
		 */
		override public function renderSelf ( ) : void 
		{
			super.renderSelf ( );
			
			// Recalculate our from and to containers. We specify which 
			// transitions we are responsible for via our transitions attribute.
			// This is a comma-separated list of specifications of the form
			// fromIndex:toIndex. An index is a value that when passed to
			// getContainerByIndex returns the desired container. Escape colons,
			// asterisks, backslashes, dashes and commas in indexes with a 
			// backslash. Use a * or nothing to match all containers. Don't 
			// specify anything for the attribute if it is to handle all 
			// transitions.
			//
			
			transitions = null;
			
			if ( Util.isEmpty.test ( getAttribute ( "transitions" ) ) ) return;
			
			var specs : Array = Util.splitWithGroupings ( getAttribute ( "transitions" ), /\s*([^\\]?),\s*/, [ "pre" ] );
			
			for each ( var spec : String in specs )
			{
				// Replace \: with a token that we can fixup later so we can do
				// a simple split.
				//
				var pieces : Array = spec.replace ( "\\:", "__backslash_colon__" ).split ( /\s*:\s*/ );
				
				if ( pieces.length != 2 ) Debugger.logMessage ( Debugger.WARNING, "Transitioner", "invalid transitions specification \"" + spec + "\"" );
				
				var transition : Object = { from: null, to: null };
				
				for ( var i : uint = 0; i < 2; i++ )
				{
					// Is it a range of indexes, e.g., *:Home - 5? Check here so
					// that the user can escape the hyphen to avoid the range
					// functionality.
					//
					
					var rangeTest : RegExp = /^(.+) - (.+)$/;
					var rangeResult : Array = rangeTest.exec ( pieces [ i ] );
					
					pieces [ i ] = pieces [ i ].replace ( "__backslash_colon__", "\\:" );
					pieces [ i ] = pieces [ i ].replace ( "\\", "" );
				
					var matches : Array = [ ];
					
					if ( pieces [ i ].length > 0 && pieces [ i ] != "*" ) 
					{
						var indexes : Array = [ ];
						
						if ( rangeResult != null && rangeResult.length == 3 )
						{
							var startContainer  : Container = getContainerByIndex ( rangeResult [ 1 ] );
							var finishContainer : Container = getContainerByIndex ( rangeResult [ 2 ] );
							
							if ( startContainer == null ) 
							{
								Debugger.logMessage ( Debugger.WARNING, "Transitioner", "cannot find container \"" + rangeResult [ 1 ] + "\" in transitions specification \"" + spec + "\"" );
							}
							else if ( finishContainer == null ) 
							{
								Debugger.logMessage ( Debugger.WARNING, "Transitioner", "cannot find container \"" + rangeResult [ 2 ] + "\" in transitions specification \"" + spec + "\"" );
							}
							else
							{
								for ( var j : uint = Math.min ( startContainer.childIndex, finishContainer.childIndex ); j <= Math.max ( startContainer.childIndex, finishContainer.childIndex ); j++ )
								{
									matches.push ( getContainerByIndex ( String ( j + 1 ) ) );
								}
							}
						}
						else
						{
							var container : Container = getContainerByIndex ( pieces [ i ] );
							
							if ( container == null ) 
							{
								Debugger.logMessage ( Debugger.WARNING, "Transitioner", "cannot find container \"" + pieces [ i ] + "\" in transitions specification \"" + spec + "\"" );
							}
							else
							{
								matches.push ( container );
							}
						}
					}
					
					transition [ i == 0 ? "from" : "to" ] = matches;
				}
				
				if ( transitions == null ) transitions = [ ];
				
				transitions.push ( transition );
			}
		}
		
		public function willHandleJump ( from : Container, to : Container ) : Boolean
		{
			var result : * = runInteractions ( "willHandleJump", { from: from, to: to } );
			if ( result === true || result === false ) return result;
			
			if ( transitions == null ) return true;
			
			var likesFrom : Boolean = false;
			var likesTo : Boolean = false;
			
			for ( var i : uint = 0; i < transitions.length; i++ )
			{
				if ( transitions [ i ].from.length == 0 || transitions [ i ].from.indexOf ( from ) >= 0 ) likesFrom = true;
				if ( transitions [ i ].to.length   == 0 || transitions [ i ].to.indexOf ( to )     >= 0 ) likesTo   = true;
			}
			
			return likesFrom && likesTo;
		}
		
		public function start ( afterDelay : Boolean = false ) : void
		{
			started = true;
			
			if ( afterDelay ) 
			{
				if ( delayTaskId != 0 ) delayTaskId = Scheduler.getInstance ( ).removeTask ( delayTaskId );
				dispatchEvent ( new FrontalEvent ( FrontalEvent.START ) );
				return;
			}
			else
			{
				dispatchEvent ( new FrontalEvent ( FrontalEvent.INIT ) );
			}
			
			if ( Util.isEmpty.test ( getAttribute ( "delay" ) ) )
			{
				start ( true );
			}
			else 
			{
				if ( isNaN ( getAttribute ( "delay" ) ) )
				{
					Debugger.logMessage ( Debugger.WARNING, "Transitioner", "delay \"" + getAttribute ( "delay" ) + "\" must be a number" );
				}
				else
				{
					if ( delayTaskId != 0 ) delayTaskId = Scheduler.getInstance ( ).removeTask ( delayTaskId );
					if ( Util.isTrue.test ( getAttribute ( "useSeconds" ) ) )
					{
						delayTaskId = Scheduler.setTimeout ( start, Number ( getAttribute ( "delay" ) ), true, true );
					}
					else
					{
						delayTaskId = Scheduler.setTimeoutInFrames ( start, Number ( getAttribute ( "delay" ) ), true );
					}
				}
			}
		}
		
		public function get duration ( ) : Number
		{
			// Return the duration of this transition in frames. Include any 
			// initial delay and take into account the state of current and next
			// containers. The ContainerManager will use the maximum duration of 
			// all the transitions to create a series of progress events.
			//
			return ! Util.isEmpty.test ( getAttribute ( "delay" ) ) && ! isNaN ( getAttribute ( "delay" ) ) ? Number ( getAttribute ( "delay" ) ) : 0;
		}
		
		public function get durationIsInFrames ( ) : Boolean
		{
			// Return the units (true for frames, false for seconds) of the
			// duration return by duration ( ).
			//
			return ! Util.isTrue.test ( getAttribute ( "useSeconds" ) );
		}
		
		public function fforward ( ) : void
		{
			if ( delayTaskId != 0 ) delayTaskId = Scheduler.getInstance ( ).removeTask ( delayTaskId );
			dispatchEvent ( new FrontalEvent ( FrontalEvent.FFORWARD ) );
			transitionDone ( );
		}
		
		public function stop ( ) : void
		{
			if ( delayTaskId != 0 ) Scheduler.getInstance ( ).pauseTask ( delayTaskId );
			dispatchEvent ( new FrontalEvent ( FrontalEvent.STOP ) );
		}
		
		public function resume ( ) : void
		{
			if ( delayTaskId != 0 ) Scheduler.getInstance ( ).resumeTask ( delayTaskId );
			dispatchEvent ( new FrontalEvent ( FrontalEvent.RESUME ) );
		}
		
		public function transitionDone ( ) : void
		{
			if ( delayTaskId != 0 ) delayTaskId = Scheduler.getInstance ( ).removeTask ( delayTaskId );
			if ( started ) 
			{
				started = false;
				dispatchEvent ( new FrontalEvent ( FrontalEvent.TRANSITION_DONE ) );
				containerManager.transitionDone ( this );
			}
		}
		
		/**            
		 * @private
		 */
		override public function applyInteractions ( ) : void
		{
			super.applyInteractions ( );
			
			// Handle our standard movie clip events. For these, we look for an
			// attribute like "on" + the captialized event string, e.g., onClick
			// or onRollOut.
			//
			
			applyInteraction ( FrontalEvent.INIT );
			applyInteraction ( FrontalEvent.START );
			applyInteraction ( FrontalEvent.FFORWARD );
			applyInteraction ( FrontalEvent.STOP );
			applyInteraction ( FrontalEvent.RESUME );
			applyInteraction ( FrontalEvent.TRANSITION_DONE );
		}
    }
}

